package agentspeakeditor.editors.common.parser;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.eclipse.jface.text.IDocument;
import org.xml.sax.SAXParseException;

import agentspeakeditor.editors.ColorManager;
import agentspeakeditor.editors.common.AST.AST;
import agentspeakeditor.editors.common.AST.Program;
import agentspeakeditor.editors.common.AST.Expression.ExprBase;
import agentspeakeditor.editors.common.AST.Expression.ExprBaseId;
import agentspeakeditor.editors.common.AST.Expression.ExprBaseNot;
import agentspeakeditor.editors.common.AST.Expression.ExprBasePar;
import agentspeakeditor.editors.common.AST.Expression.ExprBaseRelation;
import agentspeakeditor.editors.common.AST.Expression.LogicExpr;
import agentspeakeditor.editors.common.AST.Line.Belief;
import agentspeakeditor.editors.common.AST.Line.Body;
import agentspeakeditor.editors.common.AST.Line.BoolContext;
import agentspeakeditor.editors.common.AST.Line.Command;
import agentspeakeditor.editors.common.AST.Line.Comment;
import agentspeakeditor.editors.common.AST.Line.Context;
import agentspeakeditor.editors.common.AST.Line.Event;
import agentspeakeditor.editors.common.AST.Line.InitialDesire;
import agentspeakeditor.editors.common.AST.Line.Line;
import agentspeakeditor.editors.common.AST.Line.LogicContext;
import agentspeakeditor.editors.common.AST.Line.Plan;
import agentspeakeditor.editors.common.AST.Terminal.BoolConstant;
import agentspeakeditor.editors.common.AST.Terminal.ID;
import agentspeakeditor.editors.common.AST.Terminal.LogicOperator;
import agentspeakeditor.editors.common.AST.Terminal.Number;
import agentspeakeditor.editors.common.AST.Terminal.RelationalOperator;
import agentspeakeditor.editors.common.scanner.Scanner;
import agentspeakeditor.editors.common.scanner.Token;
import agentspeakeditor.editors.common.scanner.TokenType;
import agentspeakeditor.editors.marker.ASLMarkerErrorHandler;

public class ASLParser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	private ColorManager manager= new ColorManager();
	private ASLMarkerErrorHandler maskerError;
	private boolean hasErrors;
	/**
	 * Parser construcurrentTokenor
	 */
	public ASLParser() {
		// Initializes the scanner
		this.scanner = new Scanner(manager,true);	;
		hasErrors = false;
	}
	
	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException 
	 * @throws SyntaticException
	 */ //TODO
	private void accept(TokenType kind) throws SyntacticException {
		// If the current token kind is equal to the expected one
		// Gets next token
		if(this.currentToken.getKind() == kind) {
			this.currentToken = (Token) this.scanner.getNextToken();
		}else {
			hasErrors = true;
		}
	}
	
	private void acceptIt() {
		currentToken = (Token) this.scanner.getNextToken();
	}
	
	public AST parse(IDocument document) throws SyntacticException {
		scanner.setRange(document, 0, document.getLength());
		
		currentToken = (Token) scanner.getNextToken();
		
		AST ast = new Program(parseProg());
		accept(TokenType.EOT);
		return ast; 
	}
	
	private Program parseProg() throws SyntacticException  {
		Program p = new Program();
		while(currentToken.getKind() != TokenType.EOT) {
			p.getLines().add(parseLine());
			//accept(TokenType.EOL);
		}
		return p;
	}
	
	private Line parseLine() throws SyntacticException {
		Line line = null;
		
		if (currentToken.getKind() == TokenType.EXC) {			
			acceptIt();
			ID id = parseID();
			accept(TokenType.PON);
			
			line = new InitialDesire(id);
		}
		else if (currentToken.getKind() == TokenType.ID) {
			ID id = new ID(currentToken.getSpelling());
			acceptIt();
			accept(TokenType.PARE);
			
			Number number = new Number(currentToken.getSpelling());
			acceptIt();
			
			accept(TokenType.PARD);
			accept(TokenType.PON);
			
			
			line = new Belief(id, number);
		}
		else if (currentToken.getKind() == TokenType.BARRA) {
			acceptIt();
			if(currentToken.getKind() == TokenType.BARRA){
				acceptIt();
				line = new Comment(currentToken.getSpelling());
				acceptIt();
				
				accept(TokenType.EOL);
			}
			else if(currentToken.getKind() == TokenType.AST){
				acceptIt();
				line = new Comment(currentToken.getSpelling());
				acceptIt();
				
				accept(TokenType.AST);
				accept(TokenType.BARRA);
			}
		}
		else{
			Event event = parseEvent();
			Context ctxt = parseContext();
			Body body = parseBody();
			
			accept(TokenType.PON);
			line = new Plan(event,ctxt,body);
		}
		
		if(hasErrors)
			return line = null;
		
		return line;
	}
	
	private Event parseEvent() throws SyntacticException {
		Event event = null;
		
		if(currentToken.getKind() == TokenType.MAIS || currentToken.getKind() == TokenType.MENOS){
			acceptIt();	
			event = new Event(parseID());					
		} else if(currentToken.getKind() == TokenType.MAIS_EXC || currentToken.getKind() == TokenType.MENOS_EXC || 
				currentToken.getKind() == TokenType.MAIS_INT || currentToken.getKind() == TokenType.MENOS_INT) {
			acceptIt();	
			event = new Event(parseID());			
		}
		return event;
	}
	
	private Context parseContext() throws SyntacticException {
		Context ctxt = null;		
		if(currentToken.getKind() == TokenType.DPON)
		{
			acceptIt();
			if(currentToken.getKind() == TokenType.TRUE || currentToken.getKind() == TokenType.FALSE)
			{
				ctxt = new BoolContext(new BoolConstant(currentToken.getSpelling()));
				acceptIt();
			}
			else
				ctxt = new LogicContext(parseExpr());			
		}
		return ctxt;
	}

	private Body parseBody() throws SyntacticException {
		Body body = null;		
		
		if(currentToken.getKind() == TokenType.SET){
			acceptIt();
			ArrayList<Command> cmds = new ArrayList<Command>();
			cmds.add(parseCommand());
			while(currentToken.getKind() == TokenType.PVIR){
				acceptIt();
				cmds.add(parseCommand());
			}
			
			body = new Body(cmds);
		}
		return body;
	}
	
	private LogicExpr parseExpr() throws SyntacticException  {
		List<ExprBase> list = new Vector<ExprBase>();
		List<LogicOperator> operadores = new Vector<LogicOperator>();
		
		list.add(parseLogicExprBase());
		while (currentToken.getKind() == TokenType.E || currentToken.getKind() == TokenType.OU) {
			operadores.add(new LogicOperator(currentToken.getSpelling()));
			acceptIt();
			list.add(parseLogicExprBase());
		}
		
		return new LogicExpr(list, operadores);
	}
	
	private ExprBase parseLogicExprBase() throws SyntacticException  {
		
		if (currentToken.getKind() == TokenType.ID) {
			ID id1 = parseID();
			if (currentToken.getKind() == TokenType.MAIOR || currentToken.getKind() == TokenType.MAIG ||
					currentToken.getKind() == TokenType.MENOR || currentToken.getKind() == TokenType.MEIG ||
					currentToken.getKind() == TokenType.IG || currentToken.getKind() == TokenType.DIF) {
				RelationalOperator op = new RelationalOperator(currentToken.getSpelling());
				acceptIt();
				ID id2 = parseID();
				return new ExprBaseRelation(id1, op, id2);
			} else {
				return new ExprBaseId(id1);
			}
		} else if (currentToken.getKind() == TokenType.NOT) {
			acceptIt();
			return new ExprBaseNot(parseExpr());
		} else if (currentToken.getKind() == TokenType.PARE) {
			acceptIt();
			ExprBasePar par = new ExprBasePar(parseExpr());
			accept(TokenType.PARD);
			return par;
		}
		return null;
	}	
	
	private Command parseCommand() throws SyntacticException {
		ArrayList<ID> ids = new ArrayList<ID>();
		
		if(currentToken.getKind() == TokenType.PON){
			acceptIt();
		}					
		ID id = parseID();
		accept(TokenType.PARE);
		
		ids.add(parseID());
		while(currentToken.getKind() == TokenType.VIR){
			acceptIt();
			ids.add(parseID());
		}
		accept(TokenType.PARD);
		
		return new Command(id,ids);
	}
	
	private ID parseID() throws SyntacticException  {
		if (this.currentToken.getKind() == TokenType.ID) {
			ID id = new ID(currentToken.getSpelling());
			acceptIt();
			
			return id;
		} else {
			accept(TokenType.ID);
			return null;
		}
	}

	public void setErrorHandler(ASLMarkerErrorHandler markingErrorHandler) {
		this.maskerError = markingErrorHandler;
		
	}
}